package com.example.localareanetworkchat.fragment

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.example.localareanetworkchat.constant.ChatConstant
import com.example.localareanetworkchat.fragment.entity.*
import com.example.localareanetworkchat.persistence.dao.ChatGroupDao
import com.example.localareanetworkchat.persistence.dao.ChatGroupUserDao
import com.example.localareanetworkchat.persistence.dao.ChatMessageDao
import com.example.localareanetworkchat.persistence.database.ChatDatabase
import com.example.localareanetworkchat.persistence.entity.*
import com.example.localareanetworkchat.persistence.factory.ChatDatabaseFactory
import com.fasterxml.jackson.databind.ObjectMapper
import com.koushikdutta.async.callback.DataCallback
import com.koushikdutta.async.http.AsyncHttpClient
import com.koushikdutta.async.http.AsyncHttpPost
import com.koushikdutta.async.http.server.AsyncHttpServer
import com.koushikdutta.async.http.server.AsyncHttpServerResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import kotlin.collections.ArrayList

class MainViewModel(application: Application) : AndroidViewModel(application) {
    private val TAG = MainViewModel::class.simpleName

    private var chatDatabase: ChatDatabase?= null
    private var chatGroupDao: ChatGroupDao?= null
    private var chatGroupUserDao: ChatGroupUserDao ? =null
    private var chatMessageDao: ChatMessageDao ?= null
    var user: User ?= null
    var chatGroup: ChatGroup ?= null

    var port = ChatConstant.CHAT_HTTP_SERVER_PORT

    private val chatHttpServer = AsyncHttpServer()

    init {
        chatDatabase = ChatDatabaseFactory(getApplication()).chatDatabase
        chatGroupDao = chatDatabase!!.chatGroupDao()
        chatGroupUserDao = chatDatabase!!.chatGroupUserDao()
        chatMessageDao = chatDatabase!!.chatMessageDao()
    }

    fun generateInvitationCode(): String {
        val ipSeg = ipToHexStr(getIp())
        val randomStr = UUID.randomUUID().toString().substring(0, 4).uppercase()

        return ipSeg + randomStr
    }

    private fun ipToHexStr(ipStr: String): String {

        val ipPart = ipStr.split(".")
        var result = 0
        result = result or ipPart[0].toInt()
        result = result shl 8
        result = result or ipPart[1].toInt()
        result = result shl 8
        result = result or ipPart[2].toInt()
        result = result shl 8
        result = result or ipPart[3].toInt()

        val r = Integer.toHexString(result).uppercase()

        return if (r.length == 8) r
        else {
            "0$r"
        }
    }

    private fun ipHexToStr(ipStr: String): String {

        var ip = ""
        ip += ipStr.substring(0, 2).toLong(16).toString() + "."
        ip += ipStr.substring(2, 4).toLong(16).toString() + "."
        ip += ipStr.substring(4, 6).toLong(16).toString() + "."
        ip += ipStr.substring(6, 8).toLong(16).toString()

        return ip
    }

    fun getNetStatus(): Int {
        var status = 0
        val connMgr = getApplication<Application>().applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        connMgr.allNetworks.forEach { network ->
            val networkInfo = connMgr.getNetworkInfo(network)
            if (networkInfo?.getType() == ConnectivityManager.TYPE_WIFI) {
                if (networkInfo.isConnected()){
                    status = status or 1
                }
            }
            if (networkInfo?.getType() == ConnectivityManager.TYPE_MOBILE) {
                if (networkInfo.isConnected()){
                    status = status or 2
                }
            }
        }
        return status
    }
    private fun getIp(): String{
        val wifiManager = getApplication<Application>().applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
        val wifiInfo = wifiManager.connectionInfo
        return intToIp(wifiInfo.ipAddress)
    }
    private fun intToIp(ipInt: Int): String {
        val sb = StringBuilder()
        sb.append(ipInt and 0xFF).append(".")
        sb.append((ipInt shr 8) and 0xFF).append(".")
        sb.append((ipInt shr 16) and 0xFF).append(".")
        sb.append((ipInt shr 24) and 0xFF)
        return sb.toString()
    }

    private fun getDhcpIp(): Int {
        val wifiManager = getApplication<Application>().applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
        val dhcpInfo = wifiManager.dhcpInfo
        return dhcpInfo.ipAddress
    }

    fun insertChatGroup(chatGroup: ChatGroup) {

        val chatGroupMetaData = ChatGroupMetaData(chatGroup.uid, chatGroup.groupName, chatGroup.chatGroupId,
            chatGroup.createTime, 0, 0, getDhcpIp())
        chatGroup.groupMetaData = ObjectMapper().writeValueAsString(chatGroupMetaData)
        chatGroup.dhcpIp = getDhcpIp()

        chatGroupDao?.insert(chatGroup)
    }

    fun insertChatGroup(chatGroup: ChatGroup, user: User) {

        val chatGroupMetaData = ChatGroupMetaData(chatGroup.uid, chatGroup.groupName, chatGroup.chatGroupId,
            chatGroup.createTime, 0, 0, getDhcpIp())
        chatGroup.groupMetaData = ObjectMapper().writeValueAsString(chatGroupMetaData)
        chatGroup.dhcpIp = getDhcpIp()

        if (chatGroup.joinAddress.isEmpty()){
            chatGroup.joinAddress = getIp() + ":" + port
        }

        chatGroupDao?.insert(chatGroup)
        val chatGroupUser = ChatGroupUser(0, chatGroup.groupName, user.id, user.name, user.uid, chatGroup.chatGroupId, chatGroup.dhcpIp, chatGroup.joinAddress)
        insertChatGroupUser(chatGroupUser)
    }

    private fun insertChatGroupUser(chatGroupUser: ChatGroupUser){
        chatGroupUserDao?.insert(chatGroupUser)
    }

    fun joinGroup(user: User, remoteAddress: String, selfAddress: String, invitationCode: String){
        viewModelScope.launch(Dispatchers.IO){
            val alreadyInChatGroup = chatGroupDao?.queryByGroupInvitationCode(invitationCode)
            // 用于支持 group 可删除的情况
            if (alreadyInChatGroup != null){
                viewModelScope.launch(Dispatchers.Main){
                    Toast.makeText(getApplication(), ChatResponseDataType.JOIN_GROUP_ALREADY_IN, Toast.LENGTH_SHORT).show()
                }
                return@launch
            }
            var remoteHost = remoteAddress
            var selfHost = selfAddress
            if (remoteAddress.isEmpty() or selfAddress.isEmpty()){
                remoteHost = ipHexToStr(invitationCode.substring(0, 9)) + ":" + ChatConstant.CHAT_HTTP_SERVER_PORT
                selfHost = getIp() + ":" + port
            }

            val joinGroupData = JoinGroupRequestData(invitationCode, "", selfHost, user.name, user.uid)
            val chatData = ChatRequestData(ChatRequestDataType.JOIN_GROUP, ObjectMapper().writeValueAsString(joinGroupData))

            val joinUrl = "http://$remoteHost/chat?data=" + ObjectMapper().writeValueAsString(chatData)
            Log.i(TAG, "joinGroup remote host: $remoteHost")
            AsyncHttpClient.getDefaultInstance().execute(AsyncHttpPost(joinUrl)) { e, response ->
                if (e != null){
                    Log.i(TAG, "joinGroup: " + e.printStackTrace())
                }else {
                    response.dataCallback = DataCallback { _, bb ->
                        try {
                            val chatResponseData = ObjectMapper().readValue(bb.readString(), ChatResponseData::class.java)
                            when(chatResponseData.status){
                                "0" -> {
                                    val joinGroupResponseData = ObjectMapper().readValue(chatResponseData.data, JoinGroupResponseData::class.java)
                                    when(joinGroupResponseData.type){
                                        ChatResponseDataType.JOIN_GROUP_OK -> {
                                            //viewModelScope.launch(Dispatchers.IO){
                                                val chatGroup = joinGroupResponseData.chatGroup!!
                                                chatGroup.userId = user.id
                                                chatGroup.uid = user.uid
                                                chatGroup.joinAddress = selfAddress
                                                chatGroup.groupType = GroupAddType.GROUP_ADD_TYPE_JOIN
                                                chatGroup.groupSubInvitationCode = generateInvitationCode()
                                                chatGroupDao?.insert(chatGroup)

                                                chatGroupUserDao?.insertAll(joinGroupResponseData.chatGroupUserList!!)

                                                viewModelScope.launch(Dispatchers.Main){
                                                    Toast.makeText(getApplication(), "群组加入成功", Toast.LENGTH_SHORT).show()
                                                }

                                                // 发送消息到其他user
                                                joinGroupResponseData.chatGroupUserList?.filter {
                                                    it.uid !in joinGroupResponseData.groupPairUid!!
                                                }?.forEach {
                                                    notifyChatGroupOtherUser(user, chatGroup, it)
                                                }
                                            //}
                                        }
                                        else -> {
                                            viewModelScope.launch(Dispatchers.Main){
                                                Toast.makeText(getApplication(), joinGroupResponseData.type, Toast.LENGTH_SHORT).show()
                                            }
                                        }
                                    }
                                }
                                else -> {
                                    viewModelScope.launch(Dispatchers.Main){
                                        Toast.makeText(getApplication(), chatResponseData.message, Toast.LENGTH_SHORT).show()
                                    }
                                }
                            }
                        }catch (e: Exception){
                            Log.i(TAG, "joinGroup response 数据异常")
                        }
                    }
                }
            }
        }

    }

    private fun notifyChatGroupOtherUser(user: User, chatGroup: ChatGroup, chatGroupUser: ChatGroupUser){
        Log.i(TAG, "chatGroupUserCountUpdate user name: " + chatGroupUser.userName)
        val joinGroupData = JoinGroupRequestData("", chatGroup.chatGroupId, chatGroupUser.connectAddress, user.name, user.uid)
        val chatData = ChatRequestData(ChatRequestDataType.JOIN_GROUP_FROM_OTHER_USER, ObjectMapper().writeValueAsString(joinGroupData))

        val joinUrl = "http://${chatGroupUser.connectAddress}/chat?data=" + ObjectMapper().writeValueAsString(chatData)
        AsyncHttpClient.getDefaultInstance().execute(AsyncHttpPost(joinUrl)) { e, response ->
            if (e != null){
                Log.i(TAG, "notifyChatGroupOtherUser: 网络错误")
            }else {
                response.dataCallback = DataCallback { _, bb ->
                    try {
                        val chatResponseData = ObjectMapper().readValue(bb.readString(), ChatResponseData::class.java)
                        when(chatResponseData.status){
                            "0" -> {
                                Log.i(TAG, "notifyChatGroupOtherUser ok: " + chatGroupUser.userName)
                            }
                            else -> {
                                Log.i(TAG, "notifyChatGroupOtherUser error")
                            }
                        }
                    }catch (e: Exception){
                        Log.i(TAG, "notifyChatGroupOtherUser response 数据异常")
                    }
                }
            }
        }
    }

    fun initChatHttpServer(){
        //chatHttpServer.listen(ChatConstant.CHAT_HTTP_SERVER_PORT)
        chatHttpServer.listen(port)
        //Log.i(TAG, "initChatHttpServer start at: " + ChatConstant.CHAT_HTTP_SERVER_PORT)
        Log.i(TAG, "initChatHttpServer start at: $port")
//        chatHttpServer.get(ChatConstant.CHAT_HTTP_SERVER_URI) { request, response ->
//
//        }
        chatHttpServer.post(ChatConstant.CHAT_HTTP_SERVER_URI) { request, response ->
            Log.i(TAG, "request: " + request.query.getString("data"))
            val data = request.query.getString("data")
            if (data == null){
                Log.i(TAG, "initChatHttpServer: " + "http通信数据异常 data=null}")
                return@post
            }

            try {
                val chatData = ObjectMapper().readValue(data, ChatRequestData::class.java)
                when(chatData.type){
                    ChatRequestDataType.JOIN_GROUP -> {
                        joinGroup(chatData, response)
                    }
                    ChatRequestDataType.JOIN_GROUP_FROM_OTHER_USER -> {
                        joinGroupFromOtherUser(chatData, response)
                    }
                    ChatRequestDataType.CHAT_GROUP_MESSAGE -> {
                        receiveMessage(chatData, response)
                    }
                    else -> {
                        Log.i(TAG, "initChatHttpServer: " + "通信数据type不匹配")
                    }
                }
            }catch (e: Exception){
                Log.i(TAG, "http server通信数据异常\n${e.printStackTrace()}")
            }

        }
    }

    fun stopHttpServer(){
        Log.i(TAG, "stopHttpServer at: " + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(
            LocalDateTime.now()))
        chatHttpServer.stop()
    }

    private fun joinGroup(chatData: ChatRequestData, response: AsyncHttpServerResponse){
        val joinGroupData = ObjectMapper().readValue(chatData.data, JoinGroupRequestData::class.java)
        Log.i(TAG, "join group, user name: " + joinGroupData.userName)
        val invitationCode = joinGroupData.invitationCode
        viewModelScope.launch(Dispatchers.IO){
            val chatGroup = chatGroupDao?.queryByGroupInvitationCode(invitationCode)
            val chatResponseData = ChatResponseData("0", "", "")

            if(chatGroup == null){
                Log.i(TAG, "join group not exist, invitation code: " + joinGroupData.invitationCode)
                val joinGroupResponseData = JoinGroupResponseData(ChatResponseDataType.JOIN_GROUP_INVITATION_CODE_ERROR, ArrayList(0), null, ArrayList(0))
                chatResponseData.data = ObjectMapper().writeValueAsString(joinGroupResponseData)
                response.send(ObjectMapper().writeValueAsString(chatResponseData))
                return@launch
            }

            val chatGroupUser = ChatGroupUser(0, chatGroup.groupName, 0, joinGroupData.userName,
                joinGroupData.uid, chatGroup.chatGroupId, chatGroup.dhcpIp, joinGroupData.joinAddress)
            insertChatGroupUser(chatGroupUser)

            val joinGroupResponseData = JoinGroupResponseData(ChatResponseDataType.JOIN_GROUP_OK, ArrayList(chatGroupUserDao?.queryByChatGroupIdAtNow(chatGroup.chatGroupId)), chatGroup, arrayListOf(chatGroup.uid, joinGroupData.uid))
            chatResponseData.data = ObjectMapper().writeValueAsString(joinGroupResponseData)
            response.send(ObjectMapper().writeValueAsString(chatResponseData))
        }
    }
    private fun joinGroupFromOtherUser(chatData: ChatRequestData, response: AsyncHttpServerResponse){
        val joinGroupData = ObjectMapper().readValue(chatData.data, JoinGroupRequestData::class.java)
        Log.i(TAG, "join group from other, user name: " + joinGroupData.userName)
        val chatGroupId = joinGroupData.chatGroupId
        viewModelScope.launch(Dispatchers.IO){
            val chatGroup = chatGroupDao?.queryByChatGroupId(chatGroupId)
            val chatResponseData = ChatResponseData("0", "", "")

            if(chatGroup == null){
                Log.i(TAG, "join group not exist, chat group id: " + joinGroupData.chatGroupId)
                val joinGroupResponseData = JoinGroupResponseData(ChatResponseDataType.JOIN_GROUP_ID_ERROR, ArrayList(0), null, ArrayList(0))
                chatResponseData.data = ObjectMapper().writeValueAsString(joinGroupResponseData)
                response.send(ObjectMapper().writeValueAsString(chatResponseData))
                return@launch
            }

            if(chatGroupUserDao?.queryByUidAndChatGroupId(joinGroupData.uid, chatGroup.chatGroupId) != null){
                val joinGroupResponseData = JoinGroupResponseData(ChatResponseDataType.JOIN_GROUP_ALREADY_IN, ArrayList(0), null, ArrayList(0))
                chatResponseData.data = ObjectMapper().writeValueAsString(joinGroupResponseData)
                response.send(ObjectMapper().writeValueAsString(chatResponseData))
                return@launch
            }

            val chatGroupUser = ChatGroupUser(0, chatGroup.groupName, 0, joinGroupData.userName,
                joinGroupData.uid, chatGroup.chatGroupId, chatGroup.dhcpIp, joinGroupData.joinAddress)
            insertChatGroupUser(chatGroupUser)

            val joinGroupResponseData = JoinGroupResponseData(ChatResponseDataType.JOIN_GROUP_OK, ArrayList(0), null, arrayListOf())
            chatResponseData.data = ObjectMapper().writeValueAsString(joinGroupResponseData)
            response.send(ObjectMapper().writeValueAsString(chatResponseData))
        }
    }

    fun getChatMessageItemList(user: User, chatMessageList: List<ChatMessage>): List<ChatMessageItem> =
        chatMessageList.map { ChatMessageItem(it).also { chatMessageItem ->
            if (chatMessageItem.uid == user.uid){
                chatMessageItem.userSelfSendFlag = 1
            }
        } }.toMutableList()

    fun sendMessage(user: User, chatGroup: ChatGroup, message: String){

        val chatMessage = ChatMessage(0, chatGroup.id, chatGroup.groupName, user.id, user.name, user.uid, chatGroup.chatGroupId, chatGroup.joinAddress, chatGroup.dhcpIp,
        encodeMessageContent(message), ChatMessageType.PLAIN_TEXT, System.currentTimeMillis())

        viewModelScope.launch(Dispatchers.IO) {
            chatGroupUserDao?.queryByChatGroupIdAtNow(chatGroup.chatGroupId)?.forEach { chatGroupUser ->
                val chatData = ChatRequestData(ChatRequestDataType.CHAT_GROUP_MESSAGE, ObjectMapper().writeValueAsString(ChatMessageRequestData(chatMessage)))
                val joinUrl = "http://${chatGroupUser.connectAddress}/chat?data=" + ObjectMapper().writeValueAsString(chatData)
                AsyncHttpClient.getDefaultInstance().execute(AsyncHttpPost(joinUrl)) { e, response ->
                    if (e != null){
                        Log.i(TAG, "sendMessage: 网络错误")
                    }else {
                        response.dataCallback = DataCallback { _, bb ->
                            try {
                                val chatResponseData = ObjectMapper().readValue(bb.readString(), ChatResponseData::class.java)
                                when(chatResponseData.status){
                                    "0" -> {
                                        Log.i(TAG, "sendMessage ok: " + chatGroupUser.userName)
                                    }
                                    else -> {
                                        Log.i(TAG, "sendMessage error")
                                    }
                                }
                            }catch (e: Exception){
                                Log.i(TAG, "sendMessage response 数据异常")
                            }
                        }
                    }
                }
            }
        }
    }

    private fun encodeMessageContent(message: String): String = Base64.getEncoder().encodeToString(message.toByteArray())

    private fun decodeMessageContent(message: String): String = Base64.getDecoder().decode(message).decodeToString()

    private fun receiveMessage(chatData: ChatRequestData, response: AsyncHttpServerResponse){
        val chatMessage = ObjectMapper().readValue(chatData.data, ChatMessageRequestData::class.java).chatMessage
        Log.i(TAG, "receive message from user name: " + chatMessage.userName)
        val chatGroupId = chatMessage.chatGroupId

        viewModelScope.launch(Dispatchers.IO){
            val chatGroup = chatGroupDao?.queryByChatGroupId(chatGroupId)
            val chatResponseData = ChatResponseData("0", "", "")

            if(chatGroup == null){
                Log.i(TAG, "receiveMessage chat group not exist id: " + chatMessage.chatGroupId)
                val chatMessageResponseData = ChatMessageResponseData(ChatResponseDataType.JOIN_GROUP_ID_ERROR)
                chatResponseData.data = ObjectMapper().writeValueAsString(chatMessageResponseData)
                response.send(ObjectMapper().writeValueAsString(chatResponseData))
                return@launch
            }

            chatMessage.messageReceiveTimestamp = System.currentTimeMillis()
            chatMessage.message = decodeMessageContent(chatMessage.message)

            chatMessageDao?.insert(chatMessage)

            val chatMessageResponseData = ChatMessageResponseData(ChatResponseDataType.CHAT_GROUP_MESSAGE_RECEIVED_OK)
            chatResponseData.data = ObjectMapper().writeValueAsString(chatMessageResponseData)
            response.send(ObjectMapper().writeValueAsString(chatResponseData))
        }
    }

    fun deleteChatGroup(user: User, chatGroup: ChatGroup){
        viewModelScope.launch(Dispatchers.IO) {
            chatMessageDao?.deleteByUidAndChatGroupId(user.uid, chatGroup.chatGroupId)
            chatGroupUserDao?.deleteByUidAndChatGroupId(user.uid, chatGroup.chatGroupId)
            chatGroupDao?.deleteByUidAndChatGroupId(user.uid, chatGroup.chatGroupId)
        }
    }

    // 同步 chat group 的用户信息
    fun syncChatGroupUser(){

    }


    fun checkDuplicatedGroupName(groupName: String): Boolean = chatGroupDao?.queryByGroupName(groupName) != null

    fun queryAllChatGroupByUserId(userId: Long): LiveData<List<ChatGroup>>? {
        Log.i(TAG, "queryAllChatGroupByUserId: $userId" + ", dhcpIp: " + getDhcpIp())
        // return chatGroupDao?.queryAllByUserIdAndDhcp(userId, getDhcpIp())
        return chatGroupDao?.queryAllByUserId(userId)
    }

    fun queryAllChatGroupUserByChatGroupId(chatGroupId: String): LiveData<List<ChatGroupUser>>? = chatGroupUserDao?.queryByChatGroupId(chatGroupId)

    fun queryAllChatMessageByChatGroupId(chatGroupId: String): LiveData<List<ChatMessage>>? = chatMessageDao?.queryAllChatMessageByChatGroupId(chatGroupId)

}